package com.arellomobile.mvp;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import com.arellomobile.mvp.presenter.PresenterField;
import com.arellomobile.mvp.presenter.PresenterType;

/**
 * Date: 18-Dec-15
 * Time: 13:16
 *
 * @author Yuri Shmakov
 * @author Alexander Blinov
 */
public class MvpProcessor {
	public static final String PRESENTER_BINDER_SUFFIX = "$$PresentersBinder";
	public static final String PRESENTER_BINDER_INNER_SUFFIX = "Binder";
	public static final String VIEW_STATE_SUFFIX = "$$State";
	public static final String VIEW_STATE_PROVIDER_SUFFIX = "$$ViewStateProvider";

	/**
	 * <p>1) Generates tag for identification MvpPresenter</p>
	 * <p>2) Checks if presenter with tag is already exist in {@link com.arellomobile.mvp.PresenterStore}, and returns it</p>
	 * <p>3) If {@link com.arellomobile.mvp.PresenterStore} doesn't contain MvpPresenter with current tag, {@link PresenterField} will create it</p>
	 *
	 * @param <Delegated>    type of delegated
	 * @param target         object that want injection
	 * @param presenterField info about presenter from {@link com.arellomobile.mvp.presenter.InjectPresenter}
	 * @param delegateTag    unique tag generated by {@link MvpDelegate#generateTag()}   @return MvpPresenter instance
	 */
	private <Delegated> MvpPresenter<? super Delegated> getMvpPresenter(Delegated target, PresenterField<Delegated> presenterField, String delegateTag) {
		Class<? extends MvpPresenter<?>> presenterClass = presenterField.getPresenterClass();
		PresenterStore presenterStore = MvpFacade.getInstance().getPresenterStore();

		PresenterType type = presenterField.getPresenterType();
		String tag;
		if (type == PresenterType.LOCAL) {
			tag = delegateTag + "$" + presenterField.getTag(target);
		} else {
			tag = presenterField.getTag(target);
		}

		//noinspection unchecked
		MvpPresenter<? super Delegated> presenter = presenterStore.get(tag);
		if (presenter != null) {
			return presenter;
		}

		//noinspection unchecked
		presenter = (MvpPresenter<? super Delegated>) presenterField.providePresenter(target);

		if (presenter == null) {
			return null;
		}

		presenter.setPresenterType(type);
		presenter.setTag(tag);
		presenter.setPresenterClass(presenterClass);
		presenterStore.add(tag, presenter);

		return presenter;
	}

	/**
	 * <p>Gets presenters {@link java.util.List} annotated with {@link com.arellomobile.mvp.presenter.InjectPresenter} for view.</p>
	 * <p>See full info about getting presenter instance in {@link #getMvpPresenter}</p>
	 *
	 * @param delegated   class contains presenter
	 * @param delegateTag unique tag generated by {@link MvpDelegate#generateTag()}
	 * @param <Delegated> type of delegated
	 * @return presenters list for specifies presenters container
	 */
	<Delegated> List<MvpPresenter<? super Delegated>> getMvpPresenters(Delegated delegated, String delegateTag) {
		if (!hasMoxyReflector()) {
			return Collections.emptyList();
		}

		@SuppressWarnings("unchecked")
		Class<? super Delegated> aClass = (Class<Delegated>) delegated.getClass();
		List<Object> presenterBinders = null;

		while (aClass != Object.class && presenterBinders == null) {
			presenterBinders = MoxyReflector.getPresenterBinders(aClass);

			aClass = aClass.getSuperclass();
		}

		if (presenterBinders == null || presenterBinders.isEmpty()) {
			return Collections.emptyList();
		}

		List<MvpPresenter<? super Delegated>> presenters = new ArrayList<>();
		PresentersCounter presentersCounter = MvpFacade.getInstance().getPresentersCounter();
		for (Object presenterBinderObject : presenterBinders) {
			//noinspection unchecked
			PresenterBinder<Delegated> presenterBinder = (PresenterBinder<Delegated>) presenterBinderObject;
			List<PresenterField<Delegated>> presenterFields = presenterBinder.getPresenterFields();

			for (PresenterField<Delegated> presenterField : presenterFields) {
				MvpPresenter<? super Delegated> presenter = getMvpPresenter(delegated, presenterField, delegateTag);

				if (presenter != null) {
					presentersCounter.injectPresenter(presenter, delegateTag);
					presenters.add(presenter);
					presenterField.bind(delegated, presenter);
				}
			}
		}

		return presenters;
	}

	private static Boolean hasMoxyReflector = null;

	// Check is it have generated MoxyReflector without usage of reflection API
	private static boolean hasMoxyReflector() {
		if (hasMoxyReflector != null) {
			return hasMoxyReflector;
		}

		try {
			new MoxyReflector();

			hasMoxyReflector = true;
		} catch (NoClassDefFoundError error) {
			hasMoxyReflector = false;
		}

		return hasMoxyReflector;
	}
}
